package DB;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public abstract class TableAbstract<T extends RowInterface>
{
	private Connection con = DBConnection.getInstance().getDBcon();
	protected String name = "", view = "";
	protected Class<T> rowClass = null;

	/**
	 * Indsætter et objekt i tabellen
	 * @param data Objektet der skal indsættes
	 * @return Primærnøglen for det indsatte objekt
	 */
	public int insert(HashMap data)
	{
		int lastInsertedIndex = 0;

		String query = "";
		if(data.isEmpty()) //Route
		{
			query = "INSERT INTO "+name+" DEFAULT VALUES";
		}
		else
		{
			query = "INSERT INTO "+name+" (";
			Iterator iter = data.keySet().iterator();
			String columns = "", values = "";
			while(iter.hasNext())
			{
				String key = (String) iter.next();
				columns += key;
				values += data.get(key);
				if(iter.hasNext()) { columns += ", "; values += "', '"; }
			}
			query += columns+") VALUES ('"+values+"')";
		}
		

		try
		{
			Statement stmt = con.createStatement();
			stmt.setQueryTimeout(5);
			stmt.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
			ResultSet keySet = stmt.getGeneratedKeys();
			if(keySet.next())
			{
				lastInsertedIndex = keySet.getInt(1);
			}
			stmt.close();
			System.out.println("Success: "+name+" insert: "+query);
		}
		catch(SQLException ex)
		{
			System.out.println("Failure: "+name+" insert: "+query+" - message: "+ex.getMessage());
		}
		return lastInsertedIndex;
	}

	/**
	 * Indsætter en liste af objekter i tabellen
	 * @param data Objektet der skal indsættes
	 * @return Antallet af indsatte objekter
	 */
	public int[] insert(ArrayList<HashMap> data)
	{
		int[] i = new int[data.size()];
		int index = 0;
		for(HashMap rowData : data)
		{
			i[index] = insert(rowData);
			index++;
		}
		return i;
	}

	/**
	 * Opdatere et objekt i tabellen
	 * @param data En samling af objekter
	 * @param where Indikation på hvilken række der skal opdateres
	 * @return Antallet af opdaterede rækker
	 */
	public int update(HashMap data, String where)
	{
		int affectedRows = -1;

		String query = "UPDATE "+name+" SET ";
		Iterator iter = data.keySet().iterator();
		while(iter.hasNext())
		{
			String key = (String) iter.next();
			query += key+" = '"+data.get(key)+"'";
			if(iter.hasNext()) { query += ", "; }
		}
		query += ((where != null) ? " WHERE "+where : "");

		try
		{
			Statement stmt = con.createStatement();
			affectedRows = stmt.executeUpdate(query);
			stmt.close();
			System.out.println("Success: "+name+" update: "+query);
		}
		catch(SQLException ex)
		{
			System.out.println("Failure: "+name+" update: "+query+" - message: "+ex.getMessage());
		}
		return affectedRows;
	}

	/**
	 * Deaktiverer (sletter) en række i tabellen
	 * @param where Indikation på hvilken række der skal slettes
	 * @param limit Maximum antal af rækker der skal slettes
	 * @return Antallet af slettede rækker
	 */
	public int delete(String where, int limit)
	{
		int affectedRows = -1;

		HashMap data = new HashMap();
		data.put("active", "0");
		
		affectedRows = update(data, where);

		/*String query = "DELETE FROM "+name;
		query += ((where != null) ? " WHERE "+where : "");
		query += ((limit != 0) ? " LIMIT "+limit : "");
		
		try
		{
			Statement stmt = con.createStatement();
			affectedRows = stmt.executeUpdate(query);
			stmt.close();
			System.out.println("Success: "+name+" delete: "+query);
		}
		catch (SQLException e)
		{
			System.out.println("Failure: "+name+" delete: "+query+" - message: "+e.getMessage());
		}*/
		return affectedRows;
	}

	/**
	 * Sletter et objekt ved hjælp af det unikke nummer
	 * @param no Objektets nummer
	 * @return Objektet
	 */
	public int deleteByNo(int no)
	{
		return delete(getPrimaryColumnName()+" = "+no, 1);
	}

	/**
	 * Henter alle rækker i tabellen
	 * @return Samling af de hentede objekter, eller en tom liste, hvis der ingen resultater er
	 */
	public ArrayList<T> fetchAll()
	{
		return fetchAll(null, null, 0);
	}

	/**
	 * Henter alle rækker i tabellen, baseret på de givne parametre
	 * @param where Indikation på hvilken række der skal hentes
	 * @param order Bestemmer i hvilken rækkefølge objekterne bliver præsenteret
	 * @param limit Maximum antal af rækker der skal hentes
	 * @return Samling af de hentede objekter, eller en tom liste, hvis der ingen resultater er
	 */
	public ArrayList<T> fetchAll(String where, String order, int limit)
	{
		String query = "SELECT";
		query += ((limit != 0) ? " TOP "+limit : "");
		query += " * FROM "+view+" WHERE active = 1";
		query += ((where != null) ? " AND "+where : "");
		query += ((order != null) ? " ORDER BY "+order : "");
		
		ArrayList<T> rows = new ArrayList<T>();
		Statement stmt = null;
		ResultSet result = null;
		try
		{			
			stmt = con.createStatement();
			result = stmt.executeQuery(query);

			while(result.next())
			{
				T row = (T)this.getInstanceOfClass(rowClass);
				row.importData(result);
				rows.add(row);
			}
			System.out.println("Success: "+name+", results: "+rows.size()+" - fetchAll: "+query);
		}
		catch (SQLException e)
		{
			System.out.println("Failure: "+name+" fetchAll: "+query+" - message: "+e.getMessage());
			return null;
		}
		finally
		{
			try
			{
				result.close();
				stmt.close();
			}
			catch (SQLException e)
			{
				System.out.println("Failure: Couldn't close result or statement: "+e.getMessage());
			}
		}
		return rows;
	}

	/**
	 * Henter alle rækker i tabellen, baseret på de givne parametre
	 * @param where Indikation på hvilken række der skal hentes
	 * @param order Bestemmer i hvilken rækkefølge objekterne bliver præsenteret
	 * @return Samling af de hentede objekter, eller en tom liste, hvis der ingen resultater er
	 */
	public ArrayList<T> fetchAll(String where, String order)
	{
		return fetchAll(where, order, 0);
	}

	/**
	 * Henter alle rækker i tabellen, baseret på den givne parametre
	 * @param where Indikation på hvilken række der skal hentes
	 * @return Samling af de hentede objekter, eller en tom liste, hvis der ingen resultater er
	 */
	public ArrayList<T> fetchAll(String where)
	{
		return fetchAll(where, null, 0);
	}

	/**
	 * Henter en specifik række i tabellen
	 * @param where Indikation på hvilken række der skal hentes
	 * @param order Bestemmer i hvilken rækkefølge objekterne bliver præsenteret
	 * @return Det hentede objekt eller NULL, hvis der ingen resultater er
	 */
	public T fetchRow(String where, String order)
	{
		ArrayList<T> rows = fetchAll(where, order, 1);
		if(rows.size() != 0)
		{
			return (T)rows.get(0);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Henter en række ved hjælp af den unikke nummer
	 * @param no Rækkens nummer
	 * @return Række som objekt
	 */
	public T fetchByNo(int no)
	{
		return fetchRow(getPrimaryColumnName()+" = "+no, null);
	}

	/**
	 * Henter den sidst indsatte række
	 * @return Det sidst indsatte række som objekt
	 */
	public T fetchLast()
	{
		return fetchAll(null, name+"No DESC", 1).get(0);
	}

	/**
	 * Finder ud af om der findes en række med det givne nummer
	 * @param no Det unikke nummer for rækken
	 * @return true hvis rækken findes, og false hvis rækken ikke findes
	 */
	public boolean alreadyExists(int no)
	{
		if(no != 0)
		{
			if(fetchRow(name+"No = "+no, null) == null)
			{
				return false;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * Opretter ny instans af en given klasse
	 * @param theClass Den givne klasse
	 * @return Den nye instans af klassen
	 */
	private T getInstanceOfClass(Class<T> theClass)
	{
		try
		{
			return theClass.newInstance();
		}
		catch(Exception e)
		{
			System.out.println("Failure: '"+theClass.getName()+"': "+e.getMessage());
		}
		return null;
	}

	private String getPrimaryColumnName()
	{
		String columnName = "";
		if(rowClass == Models.Checkpoint.class)
		{
			columnName = "checkpointNo";
		}
		else
		{
			columnName = name+"No";
		}
		return columnName;
	}
}